<?php defined('SYSPATH') or die('No direct script access.');

require_once 'recaptchalib.php';

/**
 * Builds an HTML form. First, it collects all fields as separate objects
 * and then they are rendered using write() method.
 * Methods of form field objects usually return itself so that you can chain
 * method calls like $object->required()->style('').
 * @author
 */
class FormBuilder {

    private $entityName;
    private $defaultFieldId;
    private $entity; // associative array of entity values
    private $action = 'save';
    private $httpMethod = 'post';
    private $validationAction;
    private $displayOnly = FALSE;
    private $formId;
    private $closeForm = TRUE; // whether to close the form element or not.
    private $openForm = TRUE;
    private $secureConnection;
    
    private $fieldObjects = array();
    
    public function __construct($entityName, $entity, $defaultFieldId=null) {
        $this->entityName = $entityName;
        $this->defaultFieldId = $defaultFieldId;
        $this->entity = $entity;
    }
    
    public function secureConnection() {
        $this->secureConnection = TRUE;
    }

    public function displayOnly($displayOnly=TRUE) {
        $this->displayOnly = $displayOnly;
    }

    public function setAction($action) {
        $this->action = $action;
        return $this;
    }
    
    public function setHttpMethod($httpMethod) {
        $this->httpMethod = $httpMethod;
        return $this;
    }

    public function setValidationAction($validationAction) {
        $this->validationAction = $validationAction;
    }

    public function setFormId($formId) {
        $this->formId = $formId;
        return $this;
    }

    public function doNotCloseForm() {
        $this->closeForm = FALSE;
    }

    public function doNotOpenForm() {
        $this->openForm = FALSE;
    }
    
    /**
     * Writes HTML code at the beginning of the form.
     */
    private function writeHtmlBefore() {
        if ($this->openForm) {
            ?>
            <script type="text/javascript">
                function formOnSubmit() {
                    <?php
                    foreach ($this->fieldObjects as $field) {
                        if ($field->isRendered()) {
                            // Autocomplete functionality
                            if (method_exists($field, 'formOnSubmitScript')) {
                                echo $field->formOnSubmitScript();
                            }
                        }
                    }
                    ?>
                    return true;
                }
            </script>

            <?php
            if (! $this->displayOnly) {
                $action = $this->action;
                if (substr($action, 0, 1) != '/') {
                    // If the action is relative, compute the absolute path
                    // This is prone to errors, because the path must not contain a method name
                    // that we try to cut out.
                    $refererPath = substr($_SERVER['PHP_SELF'], strpos($_SERVER['PHP_SELF'], url::base(TRUE)) + strlen(url::base(TRUE)));
                    $controllerPath = substr($refererPath, 0, strrpos($refererPath, '/'));
                    $action = $controllerPath . '/' . $action;
                }

                $formParams = array('onsubmit' => 'return formOnSubmit()', 'method' => $this->httpMethod);
                if ($this->formId) {
                    $formParams['id'] = $this->formId;
                }
                if ($this->validationAction) {
                    $formParams['validator'] = url::site($this->validationAction);
                }

                $actionUrl = $action;
                if ($this->secureConnection && IN_PRODUCTION && $_SERVER['SERVER_ADDR'] != '127.0.0.1' && $_SERVER['SERVER_ADDR'] != '192.168.1.2') {
                	// FIXME this should be a configuration parameter
                    $actionUrl = 'https://joga.cz' . url::site($action);
                }

                echo form::open($actionUrl, $formParams);

                if ($this->entity['id']) {
                    echo '<input type="hidden" name="id" value="' . $this->entity['id'] . '" />';
                }
                if ($this->entity[sql::$ENTITY_VERSION_KEY]) {
                    echo '<input type="hidden" id="' . sql::$ENTITY_VERSION_KEY . '" name="' . sql::$ENTITY_VERSION_KEY . '" value="' . $this->entity[sql::$ENTITY_VERSION_KEY] . '" />';
                }
            }
        }

        foreach ($this->fieldObjects as $field) {
            if ($field instanceof HtmlInputHidden) {
                if ($field->isRendered()) {
                    $field->write($this->displayOnly);
                }
            }
        }

        $class = 'formTable';
        if ($this->displayOnly) {
            $class = 'displayTable';
        }
        echo '<table class="' . $class . '">';
        echo '<tbody>';
        
        // This is really necessary. TODO why?
        ob_flush();
    }
    
    /**
     * Writes HTML code at the end of the form.
     */
    private function writeHtmlAfter() {
        echo '</tbody></table>';
        if (! $this->displayOnly && $this->closeForm) {
            echo form::close();
        }


        echo '<script type="text/javascript">';
        echo '$(document).ready(function() {';
        foreach ($this->fieldObjects as $field) {
            if ($field->isRendered() && ! $field->isReadOnly() && ! $field->isDisabled()) {
                // Autocomplete functionality
                if (method_exists($field, 'delayedJavaScript')) {
                    echo $field->delayedJavaScript();
                }
            }
        }

        // Focus a default field
        /*
        if ($this->defaultFieldId) {
            echo '$("#' . $this->defaultFieldId . '").focus();' . "\n";
        }
         *
         */
        echo '});';
        echo '</script>';
    }
    
    public function write() {
        $this->writeHtmlBefore();
        foreach ($this->fieldObjects as $field) {
            if ($field->isRendered()) {
                if (! $field instanceof HtmlInputHidden) {
                    // If a programmer does not specify a default field,
                    // we will use the first one calling this method.
                    if ($this->defaultFieldId == null &&
                            $field instanceof HtmlInputField &&
                            ! $field->isDisabled() &&
                            ! $field->isReadOnly() &&
                            $field->getFieldName() != null) {
                        $this->defaultFieldId = $field->getFieldName();
                    }
                    $field->write($this->displayOnly);
                }
            }
        }
        $this->writeHtmlAfter();
    }
    
    /**
     * Adds a field to the form.
     */
    private function add($field) {
        $this->fieldObjects[] = $field;
        return $field;
    }

    /** @return HtmlInputHidden */
    public function hidden($fieldName) {
        return $this->add(new HtmlInputHidden($this->entityName, $this->entity, $fieldName));
    }

    /** @return HtmlInputText */
    public function text($fieldName) {
        return $this->add(new HtmlInputText($this->entityName, $this->entity, $fieldName));
    }

    /** @return HtmlInputAutocomplete */
    public function autocompleter($fieldName, $acValues) {
        return $this->add(new HtmlInputAutocomplete($this->entityName, $this->entity, $fieldName, $acValues));
    }

    /** @return HtmlInputI18nText */
    public function i18nText($fieldName) {
        return $this->add(new HtmlInputI18nText($this->entityName, $this->entity, $fieldName));
    }

    /** @return HtmlInputPassword */
    public function password($fieldName) {
        return $this->add(new HtmlInputPassword($this->entityName, $this->entity, $fieldName));
    }

    /** @return HtmlTextarea */
    public function textarea($fieldName) {
        return $this->add(new HtmlTextarea($this->entityName, $this->entity, $fieldName));
    }

    /** @return HtmlInputI18nTextarea */
    public function i18nTextarea($fieldName) {
        return $this->add(new HtmlInputI18nTextarea($this->entityName, $this->entity, $fieldName));
    }
    
    /** @return HtmlCheckbox */
    public function checkbox($fieldName) {
    	$field = new HtmlCheckbox($this->entityName, $this->entity, $fieldName);
    	return $this->add($field);
    }

    /** @return HtmlInputText */
    public function integer($fieldName) {
        $field = new HtmlInputText($this->entityName, $this->entity, $fieldName);
        $field->styleClass('intField');
        return $this->add($field);
    }
    
    /** @return HtmlInputText */
    public function price($fieldName, $currencyCode) {
    	$field = new HtmlInputText($this->entityName, $this->entity, $fieldName);
    	$field->styleClass('intField');
    	$field->appendix(text::get('currency.mark-' . $currencyCode));
    	return $this->add($field);
    }

    /** @return HtmlInputDecimal */
    public function decimal($fieldName) {
        return $this->add(new HtmlInputDecimal($this->entityName, $this->entity, $fieldName));
    }

    /** @return HtmlInputDate */
    public function date($fieldName, $yearRange='-15:+5') {
        return $this->add(new HtmlInputDate($this->entityName, $this->entity, $fieldName, $yearRange));
    }

    /** @return HtmlInputTime */
    public function time($fieldName) {
        return $this->add(new HtmlInputTime($this->entityName, $this->entity, $fieldName));
    }

    /** @return HtmlTimestamp */
    public function timestamp($fieldName) {
        return $this->add(new HtmlTimestamp($this->entityName, $this->entity, $fieldName));
    }

    /** @return HtmlComboBox */
    public function comboBox($fieldName, $options) {
        return $this->add(new HtmlComboBox($this->entityName, $this->entity, $fieldName, $options));
    }

    /** @return HtmlComboBox */
    public function yesNoBox($fieldName) {
        $options = array(
            0 => text::get('application.boolean-no'),
            1 => text::get('application.boolean-yes')
        );
        return $this->add(new HtmlComboBox($this->entityName, $this->entity, $fieldName, $options));
    }

    /** @return HtmlRadioButtons */
    public function radioButtons($fieldName, $values, $namePrefix=null) {
        return $this->add(new HtmlRadioButtons($this->entityName, $this->entity, $fieldName, $values, $namePrefix));
    }

    /** @return HtmlSelectMany */
    public function selectMany($fieldName, $options) {
        return $this->add(new HtmlSelectMany($this->entityName, $this->entity, $fieldName, $options));
    }

    /** @return HtmlButton */
    public function submitButton($labelKey='button.save') {
        return $this->button('submit', $labelKey);
    }

    /** @return HtmlButton */
    public function button($buttonType, $label) {
        return $this->add(new HtmlButton($buttonType, $label));
    }

    /** @return HtmlDelimiter */
    public function delimiter($label=NULL) {
        return $this->add(new HtmlDelimiter($label, false));
    }

    /** @return HtmlDelimiter */
    public function spacer() {
        return $this->add(new HtmlDelimiter(null));
    }
    
    /** @return HtmlDelimiter */
    public function description($text) {
    	return $this->add(new HtmlDescription($text));
    }

    /** @return HtmlWizardButtons */
    public function wizardButtons($wizardControllerPath) {
        return $this->add(new HtmlWizardButtons($wizardControllerPath));
    }

    /** @return HtmlDisplayField */
    public function display($fieldName) {
        return $this->add(new HtmlDisplayField($this->entityName, $this->entity, $fieldName));
    }
    
    public function captcha($fieldName) {
    	return $this->add(new HtmlRecaptcha($this->entityName, $this->entity, $fieldName));
    }
}

/*
class HtmlFormI18nFieldGroup extends HtmlInputField {
    public $fieldsArray = array();

    public function  __construct($entityName, $entity, $fiel) {
        parent::__construct($entityName, $entity, null);
        $this->fieldsArray = $fieldsArray;
    }

    public function addField($field) {
        $this->fieldsArray[] = $field;
    }

    protected function wrapLabelAndField() {
        // TODO Move this to My_Controller
        $languages = array(
                Kohana::config('yosemin.local_language'),
                Kohana::config('yosemin.language_for_l10n')
        );
        $languageCount = count($languages);
        $html = '';
        foreach ($this->fieldsArray as $field) {
            $html .= '<tr ' . ($field->isRequired() ? 'class="required"' : '') . '>';
            $label = $field->labelHtml();
            if (! $label) {
                $html .= '<td rowspan="' . $languageCount . '"></td>';
            }
            else {
                $html .= '<th rowspan="' . $languageCount . '">' . $label . '</th>';
            }
            for ($i = 0; $i < $languageCount; $i++) {
                $field->setFieldName($this->getFieldName() . '[' . $languages[$i] . ']');
                if ($i > 0) {
                    $html = '<tr>';
                }
                $html .= '<td>' . $field->fieldHtml() . '<span>' . $languages[$i] . '</span></td>';
                $html .= '</tr>';
            }
        }
        return $html;
    }
    public function write() {
        echo $this->wrapLabelAndField();
    }
}
*/

/**
 * Parent class for HTML form fields.
 */
abstract class HtmlFormField {
    
    private $disabled = FALSE;
    private $readOnly = FALSE;
    protected $required = FALSE;
    private $hidden = FALSE;
    private $rendered = TRUE;
    protected $hint = NULL;
    private $appendix;
    protected $identifyingValue;
    protected $associatedObject;
    protected $styleClass;
    protected $style;
    protected $type;
    protected $fieldName;
    protected $fieldId;

    /** @return HtmlFormField */
    public function identifyingValue($identifyingValue=TRUE) {
        $this->identifyingValue = $identifyingValue;
        return $this;
    }
    
    /** @return HtmlFormField */
    public function id($fieldId) {
    	$this->fieldId = $fieldId;
    	return $this;
    }

    /** @return HtmlFormField */
    public function disabled($disabled=TRUE) {
        $this->disabled = $disabled;
        return $this;
    }
    public function isDisabled() {
        return $this->disabled;
    }

    /** @return HtmlFormField */
    public function readOnly($readOnly=TRUE) {
        $this->readOnly = $readOnly;
        return $this;
    }
    public function isReadOnly() {
        return $this->readOnly;
    }

    /** @return HtmlFormField */
    public function rendered($rendered) {
        $this->rendered = $rendered;
        return $this;
    }
    public function isRendered() {
        return $this->rendered;
    }

    public function associatedObject($arrayKeyName) {
        $this->associatedObject = $arrayKeyName;
        return $this;
    }
    
    /** @return HtmlFormField */
    public function hidden($hidden=TRUE) {
        $this->hidden = $hidden;
        return $this;
    }
    public function isHidden() {
        return $this->hidden;
    }

    public function isRequired() {
        return $this->required;
    }
    
    /** @return Label HTML for the field. */
    protected function labelHtml($displayOnly) { return null; }
    
    /** @return Form field HTML. */
    protected function fieldHtml($displayOnly) { return null; }

    /**
     * Wraps the label and the field in formating HTML code.
     */
    protected function wrapLabelAndField($displayOnly) {
        $html = '<tr ' . ($this->isRequired() ? 'class="required"' : '');
        if ($this->hidden) {
            $html .= ' style="display: none"';
        }
        $html .= '>';

        // Label
        $label = $this->labelHtml($displayOnly);
        if (! $label) {
            $html .= '<td></td>';
        }
        else {
            $html .= '<th>' . $label . '</th>';
        }

        // Field
        $class = '';
        if ($this->identifyingValue) {
            $class = 'identifyingValue';
        }
        $html .= '<td class="' . $class . '">' . $this->fieldHtml($displayOnly);
        if ($this->appendix) {
            $html .= '<span class="formFieldAppendix">' . $this->appendix . '</span>';
        }
        $html .= '</td>';
        $html .= '</tr>';
        return $html;
    }

    protected function fieldHint() { return NULL; }

    /**
     * Writes the complete HTML code for the field.
     */
    public function write($displayOnly = FALSE) {
        echo $this->wrapLabelAndField($displayOnly);
    }

    /** @return HtmlFormField */
    public function hint($hintText=NULL) {
        if (! $hintText) {
            $hintText = $this->fieldHint();
        }
        $this->hint = $hintText;
        return $this;
    }

    /** @return HtmlFormField */
    public function appendix($text) {
        $this->appendix = $text;
        return $this;
    }

    public function styleClass($styleClass) {
        $this->styleClass = $styleClass;
        return $this;
    }

    public function style($style) {
        $this->style = $style;
        return $this;
    }

    protected function commonAttributes() {
        $attrs = '';
        if ($this->styleClass) {
            $attrs .= ' class="' . $this->styleClass . '"';
        }
        if ($this->style) {
            $attrs .= ' style="' . $this->style . '"';
        }
        if ($this->disabled) {
            $attrs .= ' disabled="disabled"';
        }
        if ($this->readOnly) {
            $attrs .= ' readonly="readonly"';
        }
        if ($this->type) {
            $attrs .= ' type="' . $this->type . '" ';
        }
        if ($this->fieldId) {
        	$attrs .= ' id="' . $this->fieldId . '" ';
        }
        return $attrs;
    }
}

class HtmlDisplayField extends HtmlInputField {

    protected $value;
    protected $moreInfoPopupUrl;

    public function __construct($entityName, $entity, $fieldName) {
        parent::__construct($entityName, $entity, $fieldName);
    }

    /** @return HtmlDisplayField */
    public function value($value) {
        $this->value = $value;
        return $this;
    }

    protected function getFieldValue() {
        $value = '';
        if ($this->value) {
            $value = $this->value;
        }
        else {
            $value = parent::getFieldValue();
        }
        return $value;
    }
    
    /** @return HtmlFormField */
    public function moreInfoPopup($url) {
        $this->moreInfoPopupUrl = $url;
        return $this;
    }

    public function fieldHtml($displayOnly) {
    	$value = html::escape($this->getFieldValue());
    	if ($this->moreInfoPopupUrl) {
    		$value = html::moreInfoPopup($this->moreInfoPopupUrl, $value);
    	}
        return '<span class="displayOnlyValue">' . $value . '</span>';
    }
}

class HtmlInputField extends HtmlFormField {
    
    protected $entity;
    protected $entityName;
    protected $label;
    protected $labelKey;
    protected $onChangeScript;
    
    public function __construct($entityName, $entity, $fieldName, $fieldId = null) {
        $this->entityName = $entityName;
        $this->entity = $entity;
        $this->fieldId = $fieldId;
        $this->setFieldName($fieldName);
    }
    
    public function setFieldName($fieldName) {
        $this->fieldName = $fieldName;
        if (! $this->fieldId) {
            $this->fieldId = $this->fieldName;
        }
    }
    public function getFieldName() {
        return $this->fieldName;
    }

    /** @return HtmlInputField */
    public function onChange($onChangeScript) {
        $this->onChangeScript = $onChangeScript;
        return $this;
    }

    protected function getFieldValue() {
        if ($this->associatedObject) {
            return $this->entity[$this->associatedObject][$this->fieldName];
        }
        return $this->entity[$this->fieldName];
    }

    protected function getInputFieldName() {
        if ($this->associatedObject) {
            return $this->associatedObject . '[' . $this->fieldName . ']';
        }
        return $this->fieldName;
    }

    /**
     * Builder method, that marks the field as required.
     * @return HtmlInputField
     */
    public function required($required = TRUE) {
        $this->required = $required;
        return $this;
    }

    /** @return HtmlInputField */
    public function label($label) {
        $this->label = $label;
        return $this;
    }

    /** @return HtmlInputField */
    public function labelKey($labelKey) {
        $this->labelKey = $labelKey;
        if (strpos($labelKey, '.') === FALSE) {
            $this->label = text::get($this->entityName . '.' . $labelKey);
        }
        else {
            $this->label = text::get($labelKey);
        }
        return $this;
    }
    
    protected function labelHtml($displayOnly) {
        $html = '<label for="' . $this->fieldId . '">';
        $label = $this->label;
        if (! $label) {
            $label = text::get($this->entityName . '.' . $this->fieldName);
        }

        $label = html::escape($label);
        if ($this->hint) {
            $html .= '<acronym title="' . $this->hint . '">' . $label . '</acronym>';
        }
        else {
            $html .= $label;
        }

        $html .= '</label>';
        return $html;
    }

    protected function fieldHint() {
        $labelKey = $this->entityName . '.' . $this->fieldName;
        if ($this->labelKey) {
            $labelKey = $this->labelKey;
        }
        $hintKey = $labelKey . '-hint';
        $hint = text::get($hintKey);
        if ($hint != $hintKey && ! is_array($hint)) {
            return $hint;
        }
        return NULL;
    }

    protected function commonAttributes() {
        $attrs = parent::commonAttributes();
        if ($this->onChangeScript) {
            $attrs .= ' onchange="' . $this->onChangeScript . '" ';
        }
        return $attrs;
    }
}

class HtmlInputHidden extends HtmlInputField {

    public function __construct($entityName, $entity, $fieldName) {
        parent::__construct($entityName, $entity, $fieldName);
    }

    public function fieldHtml($displayOnly) {
        if ($displayOnly) {
        }
        else {
            $this->type = 'hidden';
            $html = '<input ' . $this->commonAttributes() . ' name="' . $this->getInputFieldName() . '" value="' . html::escape($this->getFieldValue()) . '" />';
        }
        return $html;
    }

    public function write($displayOnly = FALSE) {
        echo $this->fieldHtml($displayOnly);
    }
}

class HtmlInputText extends HtmlInputField {
    
    private $maxLength = 255;
    private $autocomplete = TRUE;

    public function __construct($entityName, $entity, $fieldName) {
        parent::__construct($entityName, $entity, $fieldName);
    }

    /** @return HtmlInputText */
    public function maxLength($maxLength) {
        $this->maxLength = $maxLength;
        return $this;
    }

    /** @return HtmlInputText */
    public function autocompleteOff() {
        $this->autocomplete = FALSE;
        return $this;
    }
    
    public function fieldHtml($displayOnly) {
        if ($displayOnly) {
            $html = '<span class="displayOnlyValue">' . html::escape($this->getFieldValue()) . '</span>';
        }
        else {
            $this->type = 'text';
            $html = '<input ' . $this->commonAttributes() . ' name="' . $this->getInputFieldName() . '" value="' . html::escape($this->getFieldValue()) . '"  ';
            if ($this->autocomplete === FALSE) {
                $html .= ' autocomplete="off"';
            }
            $html .= ' />';
        }
        return $html;
    }

    protected function commonAttributes() {
        $attrs = parent::commonAttributes();
        if ($this->autocomplete === FALSE) {
            $attrs .= ' autocomplete="off"';
        }
        if ($this->maxLength) {
            $attrs .= ' maxlength="' . $this->maxLength . '"';
        }
        return $attrs;
    }
}

class HtmlCheckbox extends HtmlInputField {

	public function __construct($entityName, $entity, $fieldName) {
		parent::__construct($entityName, $entity, $fieldName);
		$this->type = 'checkbox';
	}

	public function fieldHtml($displayOnly) {
		if ($displayOnly) {
			$textValue = format::boolean($this->getFieldValue());
			$html = '<span class="displayOnlyValue">' . html::escape($textValue) . '</span>';
		}
		else {
			$checked = $this->getFieldValue() ? 'checked="checked"' : '';
			$html = '<input ' . $this->commonAttributes() . ' value="' . sql::$TRUE . '" name="' . $this->getInputFieldName() . '" ' . $checked . ' />';
		}
		return $html;
	}
}

class HtmlInputDecimal extends HtmlInputText {

    public function __construct($entityName, $entity, $fieldName) {
        parent::__construct($entityName, $entity, $fieldName);
        $this->styleClass = 'decimalField';
    }

    public function getFieldValue() {
        $value = parent::getFieldValue();
        return convert::toInteger($value);
    }
}

class HtmlInputAutocomplete extends HtmlInputField {

    // JavaScript array of possible values
    private $acValues;
    private $acMustMatch = FALSE;
    private $sendId = FALSE;
    private $textFieldId;
    private $textFieldName;

    public function __construct($entityName, $entity, $fieldName, $autocompleteValues) {
        parent::__construct($entityName, $entity, $fieldName);
        $this->acValues = $autocompleteValues;
        $this->textFieldId = $this->fieldId;
        $this->textFieldName = $this->fieldName;
    }

    /** @return HtmlInputAutocomplete */
    public function mustMatch() {
        $this->acMustMatch = TRUE;
        $this->sendId = TRUE;
        // Trim _id suffix, it will be used by the hidden field.
        $this->textFieldId = preg_replace('/_id$/', '', $this->fieldId);
        $this->textFieldName = preg_replace('/_id$/', '', $this->fieldName);
        return $this;
    }

    public function delayedJavaScript() {
        // I had to set multipleSeparator to something that does not appear in value strings,
        // because when mustMatch is true, the autocompleter used to split the value by a comma
        // and if there was some, it considered the string not matching.
    	if ($this->acMustMatch) {
    		return "$(document.getElementById('" . $this->fieldId . "')).combobox();\n";
    	}
        return 'Yosemin.Autocomplete.create(\'' . $this->textFieldId . '\', ' . javascript::toJsArray($this->acValues)  . ', \'' . $this->fieldId . '\', ' . javascript::toAssociativeArray($this->acValues) . ');' . "\n";
    }

    public function formOnSubmitScript() {
        $js = '';
        return $js;
    }

    public function fieldHtml($displayOnly) {
        if ($displayOnly) {
            $html = '<span class="displayOnlyValue">' . html::escape($this->getFieldValue()) . '</span>';
        }
        else {
        	if ($this->acMustMatch) {
        		// Autocomplete field should always have an empty option
        		$optionsWithEmptyFirst = form::addEmptyOption($this->acValues);
        		return form::dropdown($this->fieldName, $optionsWithEmptyFirst, $this->getFieldValue());
        	}
            $html = '';
            if ($this->sendId) {
                $hiddenValue = $this->getFieldValue();
                $this->type = 'hidden';
                $html .= '<input ' . $this->commonAttributes() . ' name="' . $this->fieldName . '" value="' . $hiddenValue . '" />';
            }

            $value = $this->entity[$this->textFieldName];
            if ($this->acMustMatch) {
                // If the value must match, it is a value from the options indexed
                // by an hidden index value.
                $value = $this->acValues[$this->entity[$this->fieldId]];
            }
            $textFieldAttrs = array (
                'id' => $this->textFieldId,
                'name' => $this->textFieldName,
                'class' => 'autoCompletedField ' . ($this->styleClass ? $this->styleClass : ''),
                'value' => $value
            );
            $html .= form::input($textFieldAttrs);
        }
        return $html;
    }
}

class HtmlInputPassword extends HtmlInputField {

    public function __construct($entityName, $entity, $fieldName) {
        parent::__construct($entityName, $entity, $fieldName);
    }

    public function fieldHtml($displayOnly) {
        if ($displayOnly) {
            $html = '<span class="displayOnlyValue">&dots;</span>';
        }
        else {
            $this->type = 'password';
            $html = '<input ' . $this->commonAttributes() . ' name="' . $this->getInputFieldName() . '" value="' . html::escape($this->getFieldValue()) . '" />';
        }
        return $html;
    }
}

/*class HtmlInputI18nField extends HtmlInputField {

    public function __construct($entityName, $entity, $fieldName) {
        parent::__construct($entityName, $entity, $fieldName);
    }

    protected function wrapLabelAndField($displayOnly) {
        // TODO Move this to My_Controller
        $languages = array(
                Kohana::config('yosemin.local_language'),
                Kohana::config('yosemin.language_for_l10n')
        );
        $languageCount = count($languages);

        $html = '';
        for ($i = 0; $i < $languageCount; $i++) {
            $html .= '<tr ' . ($this->isRequired() ? 'class="required"' : '') . '>';
            $html .= '<th>' . $this->labelHtml($languages[$i], $displayOnly) . '</th>';
            // $html .= '<tr>';
            $html .= '<td>' . $this->fieldHtml($languages[$i], $displayOnly) . '</td>';
            $html .= '</tr>';
        }
        return $html;
    }

    protected function makeLabelHtml($language, $displayOnly) {
        return '<label for="' . $this->fieldId . '-' . $language . '">' .
               html::escape(text::get($this->entityName . '.' . $this->fieldName)) . ' <span class="languageName">[' . html::escape(text::get('application.i18n-' . $language)) . ']</span>' .
               '</label>';
    }
}

class HtmlInputI18nText extends HtmlInputI18nField {
    public function fieldHtml($language, $displayOnly) {
        $value = $this->getFieldValue();
        $value = html::escape($value[$language]);
        if ($displayOnly) {
            return '<span class="displayOnlyValue">' . $value . '</span>';
        }
        else {
            $html = '<input type="text" id="' . $this->fieldId . '-' . $language . '" name="' . $this->getInputFieldName() . '[' . $language . ']" value="' . $value . '" ';
            if ($this->isDisabled()) {
                $html .= ' disabled="disabled"';
            }
            $html .= ' />';
            return $html;
        }
    }
}

class HtmlInputI18nTextarea extends HtmlInputI18nField {
    public function fieldHtml($language, $displayOnly) {
        $value = $this->getFieldValue();
        $value = html::escape($value[$language]);
        if ($displayOnly) {
            return '<span class="displayOnlyValue">' . nl2br($value) . '</span>';
        }
        else {
            $html = '<textarea id="' . $this->fieldId . '-' . $language . '" name="' . $this->getInputFieldName() . '[' . $language . ']" ';
            if ($this->isDisabled()) {
                $html .= ' disabled="disabled"';
            }
            $html .= '>' . nl2br($value) . '</textarea>';
            return $html;
        }
    }
}*/

class HtmlTextarea extends HtmlInputField {
    
    public function __construct($entityName, $entity, $fieldName) {
        parent::__construct($entityName, $entity, $fieldName);
    }

    /** @return HtmlTextarea */
    public function autocomplete($values) {
        $this->autocompleteValues = $values;
        return $this;
    }
    
    public function fieldHtml($displayOnly) {
        $value = $this->getFieldValue();
        if ($displayOnly) {
            if ($value) {
                return '<span class="displayOnlyValue">' . format::longText($value) . '</span>';
            }
            return '';
        }
        else {
        	if ($this->isReadOnly()) {
        		return form::hidden($this->getInputFieldName(), $value) . format::longText($value);
        	}
            return '<textarea ' . $this->commonAttributes() . ' name="' . $this->getInputFieldName() . '">' . html::escape($value) . '</textarea>';
        }
    }
}

class HtmlRadioButtons extends HtmlInputField {
    
    private $values;
    private $namePrefix;
    
    public function __construct($entityName, $entity, $fieldName, $values, $namePrefix) {
        parent::__construct($entityName, $entity, $fieldName);
        $this->values = $values;
        $this->namePrefix = $namePrefix;
    }
    
    public function fieldHtml($displayOnly) {
    	if ($this->values) {
	        $html = '<div class="selectOneRadio" onclick="selectManyCheckboxesClick(this,event)">';
	        foreach ($this->values as $key => $value) {
	            $optionValue = $key;
	            $optionLabel = $value;
	            if ($this->namePrefix) {
	                $optionLabel = text::get($this->namePrefix . $value);
	                $optionValue = $value;
	            }
	            $optionLabel = html::escape($optionLabel);
	            if (! $displayOnly) {
	            	$checked = '' . $this->getFieldValue() === '' . $optionValue;
	                $html .= '<label ' . ($checked ? 'class="selected"' : '') . '><input type="radio" name="' . $this->getInputFieldName() . '" value="' . $optionValue . '" ';
	                if ($checked) {
	                    $html .= ' checked="checked" ';
	                }
	                $html .= '/>' . $optionLabel . '</label>';
	            }
	            else {
	            	if ($this->getFieldValue() === $optionValue) {
	                	return '<span class="displayOnlyValue">' . html::escape($optionLabel) . '</span>';
	            	}
	            }
	        }
	        $html = substr($html, 0, -5) . '</div>';
    	}
        return $html;
    }
}

class HtmlComboBox extends HtmlInputField {
    
    public $optionList;
    private $emptyOption = TRUE;
    
    public function __construct($entityName, $entity, $fieldName, $optionList) {
        parent::__construct($entityName, $entity, $fieldName);
        $this->optionList = $optionList;
    }

    /** @return HtmlComboBox */
    public function noEmptyOption() {
        $this->emptyOption = FALSE;
        return $this;
    }
    
    private function optionElement($value, $label) {
    	$html = '<option value="' . $value . '" ';
    	// PHP considers empty string and zero as equal, so I first compare the string lenghts.
    	if (strlen($this->getFieldValue()) == strlen($value) && $this->getFieldValue() == $value) {
    		$html .= ' selected="selected" ';
    	}
    	$html .= '>' . html::escape($label) . '</option>';
    	return $html;
    }

    public function fieldHtml($displayOnly) {
        if (! $displayOnly) {
        	// TODO rewrite to use form::dropdown($this->fieldName, $this->optionList);
            // SELECT element does not support readonly attribute
            if (! $this->isReadOnly()) {
                $html = '<select ' . $this->commonAttributes() . ' name="' . $this->getInputFieldName() . '">';
                if ($this->emptyOption) {
                    $html .= '<option value=""></option>';
                }
                foreach ($this->optionList as $value => $label) {
                	if (is_array($label)) {
                		$html .= '<optgroup label="' . html::escape($value) . '">';
                		foreach ($label as $valueInGroup => $labelInGroup) {
                			$html .= $this->optionElement($valueInGroup, $labelInGroup);
                		}
                		$html .= '</optgroup>';
                	}
                	else {
                		$html .= $this->optionElement($value, $label);
                	}
                }
                $html .= '</select>';
            }
            else {
                $html = form::hiddenWithId($this->getInputFieldName(), $this->getFieldValue());
                $html .= '<span class="' . $this->styleClass . '" style="' . $this->style . '">' . html::escape($this->optionList[$this->getFieldValue()]) . '</span>';
            }
        }
        else {
            foreach ($this->optionList as $value => $label) {
                if ($this->getFieldValue() == $value) {
                    $html = '<span class="displayOnlyValue ' . $this->styleClass . '" style="' . $this->style . '">' . html::escape($label) . '</span>';
                }
            }
        }
        return $html;
    }
}

class HtmlSelectMany extends HtmlInputField {
    
    public $optionList;
    private $selectedValues;
    protected $createButton = FALSE;
    protected $popupUrl;
    private $dialogName;

    /** @return HtmlSelectMany */
    public function ajaxCreateButton($popupUrl) {
        $this->createButton = TRUE;
        $this->popupUrl = $popupUrl;
        $this->dialogName = str_replace('_ids', '', $this->fieldName) . 'Dialog';
        return $this;
    }
    
    public function __construct($entityName, $entity, $fieldName, $optionList) {
        parent::__construct($entityName, $entity, $fieldName);
        $this->optionList = $optionList;
        if ($entity[$fieldName]) {
            $this->selectedValues = $entity[$fieldName];
        }
        else {
            $this->selectedValues = array();
        }
    }
    
    public function fieldHtml($displayOnly) {
        $html = NULL;
        if (! $displayOnly) {
            $html = '<div id="' . $this->fieldId . '" class="selectManyCheckboxes" onclick="selectManyCheckboxesClick(this,event)">';
            foreach ($this->optionList as $value => $label) {
                $selected = FALSE !== array_search($value, $this->selectedValues);
                $html .= '<label';
                if ($selected) {
                    $html .= ' class="selected"';
                }
                $html .= '><input type="checkbox" name="' . $this->getInputFieldName() . '[]" value="' . html::escape($value) . '"';
                if ($selected) {
                    $html .= ' checked="checked" ';
                }
                $html .= ' />';
                $html .= html::escape($label) . '</label>';
            }
            // $html .= html::img('add.png') . '<a href="">Přidej hodnotu</a>';
            $html .= '</div>';
            if ($this->createButton) {
                $dialogName = str_replace('_ids', '', $this->fieldName) . 'Dialog';
                $html .= '<button type="button" onclick="new Dialog(\'' . url::site($this->popupUrl) . '\', \'' . $this->fieldId . '\').open()">' . html::img('add.png') . html::escape(text::get('button.addNewItem')) . '</button>';
            }
        }
        else {
            if (! $this->selectedValues) {
                $html = '&#8709;'; //&#8211;
            }
            else {
                $html = '<ul>';
                foreach ($this->optionList as $value => $label) {
                    $selected = FALSE !== array_search($value, $this->selectedValues);
                    if ($selected) {
                        $html .= '<li>' . html::escape($label) . '</li>';
                    }
                }
                $html .= '</ul>';
            }

        }
        return $html;
    }
}

class HtmlInputDate extends HtmlInputField {
    
    public $yearRange;
    private $yearOnly;
    
    public function __construct($entityName, $entity, $fieldName, $yearRange) {
        parent::__construct($entityName, $entity, $fieldName);
        $this->yearRange = $yearRange;
    }
    
    public function fieldHtml($displayOnly) {
        $value = format::date($this->getFieldValue());
        if ($displayOnly) {
            if ($this->yearOnly) {
                $value = format::year($this->getFieldValue());
            }
            return '<span class="displayOnlyValue">' . html::escape($value) . '</span>';
        }
        $this->styleClass('dateField');
        $this->type = 'text';
        return '<input ' . $this->commonAttributes() . ' name="' . $this->getInputFieldName() . '" value="' . html::escape($value) . '" />';
    }

    public function delayedJavaScript() {
        if ($this->isDisabled()) {
            return '';
        }
        return '$("#' . $this->getFieldName() . '").datepicker({ yearRange: \'' . $this->yearRange . "', dateFormat: '" . Kohana::lang('dateTimeFormat.datePicker') . "' });\n";
    }
    
    public function yearOnly() {
        $this->yearOnly = true;
        return $this;
    }
}

class HtmlInputTime extends HtmlInputField {
    
    public function __construct($entityName, $entity, $fieldName) {
        parent::__construct($entityName, $entity, $fieldName);
    }
    
    public function fieldHtml($displayOnly) {
        $value = html::escape(format::time($this->getFieldValue()));
        if ($displayOnly) {
            return '<span class="displayOnlyValue">' . $value . '</span>';
        }
        $this->styleClass('timeField');
        $this->type = 'text';
        return '<input ' . $this->commonAttributes() . ' name="' . $this->getInputFieldName() . '" value="' . $value . '" />' .
               '<span class="fieldDescription">' . html::escape(text::get('application.timeField-example')) . '</span>';
    }
}

/**
 * Timestamp is always read only.
 */
class HtmlTimestamp extends HtmlInputField {

    public function __construct($entityName, $entity, $fieldName) {
        parent::__construct($entityName, $entity, $fieldName);
    }

    public function fieldHtml($displayOnly) {
        $value = html::escape($this->getFieldValue());
        $html = '';
        if (! $displayOnly) {
            $this->type = 'hidden';
            $html .= '<input ' . $this->commonAttributes() . ' name="' . $this->getInputFieldName() . '" value="' . $value . '" />';
        }
        $html .= '<span class="displayOnlyValue">' . format::timestamp($value) . '</span>';
        return $html;
    }
}

class HtmlButton extends HtmlFormField {
    
    private $labelKey;
    private $image;
    private $onclickHandler;
    
    public function __construct($type, $labelKey) {
        $this->type = $type;
        $this->labelKey = $labelKey;

        if ($this->labelKey == 'button.save') {
            $this->image = 'button/save.png';
        }
    }

    /** @return HtmlButton */
    public function image($image) {
        $this->image = $image;
        return $this;
    }
    
    public function onclick($onclickHandler) {
    	$this->onclickHandler = $onclickHandler;
    	return $this;
    }

    protected function wrapLabelAndField($displayOnly) {
        if (! $displayOnly) {
            return parent::wrapLabelAndField($displayOnly);
        }
        return '';
    }

    public function labelHtml($displayOnly) {
        return NULL;
        /*
        return html::img(
                'button/returnBack.png',
                array(
                    'class' => 'returnBack',
                    'onclick' => "document.location.href='" . $_SERVER['HTTP_REFERER'] . "'",
                    'title' => text::get('button.returnBack')
                )
        );
        */
    }
    
    public function fieldHtml($displayOnly) {
        $html = '<button ' . $this->commonAttributes() . ' name="' . $this->type . '" ';
//         if ($this->type == 'submit') {
//             $html .= ' onclick="disableButton(this)" ';
//         }
        if ($this->onclickHandler) {
        	$html .= ' onclick="' . $this->onclickHandler . '" ';
        }
        $html .= '>';
        if ($this->image) {
            $html .= html::img($this->image);
        }
        $html .= html::escape(text::get($this->labelKey)) . '</button>';
        return $html;
    }
}

class HtmlDescription extends HtmlFormField {

    private $text;

    public function __construct($text) {
        $this->text = $text;
    }

    protected function wrapLabelAndField($displayOnly) {
       	return '<tr><td colspan="2" class="htmlDescription description">' . html::escape($this->text) . '</td></tr>';
    }
}

class HtmlDelimiter extends HtmlFormField {

    private $label;
    private $justSpace;

    public function __construct($label=NULL, $justSpace=true) {
        $this->label = $label;
        $this->justSpace = $justSpace;
    }

    protected function wrapLabelAndField($displayOnly) {
        if ($this->label) {
        	return '<tr><td colspan="2"><fieldset class="delimiter"><legend>' . html::escape($this->label) . '</legend></fieldset></td></tr>';
        }
        if ($this->justSpace) {
        	return '<tr><td colspan="2" class="formSpacer"></td></tr>';
        }
        return '<tr><td colspan="2"><hr class="delimiter" /></td></tr>';
    }
}

class HtmlWizardButtons extends HtmlFormField {
    
    private $restartEnabled = true;
    private $previousEnabled = true;
    private $nextEnabled = true;
    private $finishEnabled = false;
    
    private $restartLabel = 'button.wizard-restart';
    private $previousLabel = 'button.wizard-previous';
    private $nextLabel = 'button.wizard-next';
    private $finishLabel = 'button.wizard-finish';

    private $nextSubmitsFormId = 'wizardForm';

    private $wizardControllerPath;
    
    public function __construct($wizardControllerPath) {
        $this->wizardControllerPath = $wizardControllerPath;
    }

    protected function wrapLabelAndField($displayOnly) {
        if (! $displayOnly) {
            return parent::wrapLabelAndField($displayOnly);
        }
        return '';
    }

    public function nextSubmits($formId) {
        $this->nextSubmitsFormId = $formId;
        return $this;
    }
    
    private function button($type, $img, $enabled, $labelKey, $onclickAction=null) {
        // If $this->nextSubmitsFormId is not NULL, the next button should submit
        // another form, not this one.
        
        if (! $enabled) {
            $img = preg_replace('/^(.*)\\.(png|gif|jpg)$/', '\1-disabled.\2', $img);
        }
        
        if ($labelKey === $this->nextLabel && $this->nextSubmitsFormId) {
            $type = 'button';
        }
        $html = '<button type="' . $type . '" ' . ($enabled ? '' : 'disabled="disabled"') . ' ';
        if ($onclickAction) {
            $html .= ' onclick="document.location.href=\'' . url::site($this->wizardControllerPath . '/' . $onclickAction) . '\'; disableButton(this)"';
        }
        if ($labelKey === $this->nextLabel && $this->nextSubmitsFormId) {
            $html .= ' onclick="submitWizardForm(); disableButton(this)"';
        }
        $html .= '>';
        $html .= html::img($img) .
               html::escape(text::get($labelKey)) .
               '</button>';
        return $html;
    }
    
    public function fieldHtml($displayOnly) {
        $nextButtonClass = 'nextButtonMargin';
        if (! $this->nextEnabled) {
            if ($this->finishEnabled) {
                $finishButtonClass = $nextButtonClass;
            }
            $nextButtonClass = '';
        }

        return '<div class="restartButtonMargin">' . $this->button('button', 'button/restart.png', $this->restartEnabled, $this->restartLabel, 'restart') . '</div>' .
               '<div>' . $this->button('button', 'button/previous.png', $this->previousEnabled, $this->previousLabel, 'previous') . '</div>' .
               '<div class="' . $nextButtonClass . '">' . $this->button('submit', 'button/next.png', $this->nextEnabled, $this->nextLabel) . '</div>' .
               '<div class="' . $finishButtonClass . '">' . $this->button('submit', 'button/finish.png', $this->finishEnabled, $this->finishLabel) . '</div>';
    }

    /** @return HtmlWizardButtons */
    public function restartEnabled($bool=TRUE) { $this->restartEnabled = $bool; return $this; }
    /** @return HtmlWizardButtons */
    public function previousEnabled($bool=TRUE) { $this->previousEnabled = $bool; return $this; }
    /** @return HtmlWizardButtons */
    public function nextEnabled($bool=TRUE) { $this->nextEnabled = $bool; return $this; }
    /** @return HtmlWizardButtons */
    public function finishEnabled($bool=TRUE) { $this->finishEnabled = $bool; return $this; }
}

class HtmlRecaptcha extends HtmlInputField {

	public function __construct($entityName, $entity, $fieldName) {
        parent::__construct($entityName, $entity, $fieldName);
    }
	
	public function fieldHtml($displayOnly) {
		return recaptcha_get_html(Kohana::config('yosemin.recaptchaPublicKey'));
	}
}
